﻿using System.Collections;
using System.Collections.Generic;
using UnityEngine;

namespace zserialize
{
public interface CustomSerializeListener
{
    bool isCustomSerializable(System.Type t);
    void Write(System.IO.BinaryWriter binaryWriter, object obj, Dictionary<Object, short> objIndexMap, List<Object> objList, HashSet<System.Type> typeSet);
    object Read(System.IO.BinaryReader binaryReader, Object[] objs);
}

public static class SerializableFieldContainerHelper
{
    public const sbyte UnknownType = -1;
    public const sbyte NullType = 0;
    public const sbyte CharType = 1;
    public const sbyte SByteType = 2;
    const sbyte Int16Type = 3;
    const sbyte Int32Type = 4;
    const sbyte Int64Type = 5;
    const sbyte ByteType = 6;
    const sbyte UInt16Type = 7;
    const sbyte UInt32Type = 8;
    const sbyte UInt64Type = 9;
    const sbyte SingleType = 10;
    const sbyte DoubleType = 11;
    const sbyte StringType = 15;
    const sbyte BooleanType = 16;
    const sbyte EnumType = 20;
    const sbyte ObjectType = 30;
    public const sbyte UnityObjectType = 35;
    const sbyte ArrayType = 40;
    const sbyte ListType = 45;
    const sbyte AnimationCurveType = 60;
    const sbyte CustomType = 127;
    
    public static CustomSerializeListener serializeListener;


    public static byte[] WriteToBytes(object obj, out Object[] objs, HashSet<System.Type> typeSet)
    {
        Dictionary<Object, short> objIndexMap = new Dictionary<Object, short>();
        List<Object> objList = new List<Object>();

        System.IO.MemoryStream stream = new System.IO.MemoryStream();
        System.IO.BinaryWriter binaryWriter = new System.IO.BinaryWriter(stream);

        WriteValueToBytes(binaryWriter, obj, ObjectType, objIndexMap, objList, typeSet);

        objs = objList.ToArray();

        return stream.ToArray();
    }

    public static void WriteValueToBytes(System.IO.BinaryWriter binaryWriter, object obj, sbyte type, Dictionary<Object, short> objIndexMap, List<Object> objList, HashSet<System.Type> typeSet)
    {
        switch (type)
        {
            case UnityObjectType:
                {
                    short index = -1;
                    if (obj != null)
                    {
                        if (!objIndexMap.TryGetValue(obj as Object, out index))
                        {
                            index = (short)objList.Count;
                            objList.Add(obj as Object);

                            objIndexMap.Add(obj as Object, index);
                        }
                    }

                    binaryWriter.Write(index);
                }
                break;
            case ArrayType:
                {
                    System.Array array = obj as System.Array;
                    System.Type elemType = obj.GetType().GetElementType();

                    int arrLen = array.GetLength(0);
                    binaryWriter.Write(arrLen);

                    sbyte elemType2 = GetBinaryType(elemType);
                    binaryWriter.Write(elemType2);

                    for (int i = 0; i < arrLen; i++)
                    {
                        WriteValueToBytes(binaryWriter, array.GetValue(i), elemType2, objIndexMap, objList, typeSet);
                    }
                }
                break;
            case ListType:
                {
                    var array = obj as IList;
                    System.Type elemType = obj.GetType().GetGenericArguments()[0];

                    int arrLen = array.Count;
                    binaryWriter.Write(arrLen);

                    sbyte elemType2 = GetBinaryType(elemType);
                    binaryWriter.Write(elemType2);

                    for (int i = 0; i < arrLen; i++)
                    {
                        WriteValueToBytes(binaryWriter, array[i], elemType2, objIndexMap, objList, typeSet);
                    }
                }
                break;
            case ObjectType:
                {
                    System.Type t = obj.GetType();

                    System.Reflection.FieldInfo[] fields = GetSerializableFields(t);

                    WriteValueToBytes(binaryWriter, t.Name, StringType, objIndexMap, objList, typeSet);

                    int arrLen = fields.Length;
                    binaryWriter.Write(arrLen);

                    if (fields != null && fields.Length > 0)
                    {
                        for (int i = 0; i < fields.Length; ++i)
                        {
                            var fi = fields[i];
                            var value = fi.GetValue(obj);

                            binaryWriter.Write(fi.Name);

                            sbyte type2 = GetBinaryType(value == null? null: fi.FieldType);                                
                            binaryWriter.Write(type2);
                            WriteValueToBytes(binaryWriter, value, type2, objIndexMap, objList, typeSet);
                        }
                    }

                    typeSet.Add(t);
                }
                break;

                case CustomType:
                {
                    if(serializeListener != null)
                    {
                        serializeListener.Write(binaryWriter, obj, objIndexMap, objList, typeSet);
                    }
                }
                break;

            

            case AnimationCurveType:
                {
                    AnimationCurve ac = obj as AnimationCurve;
                    binaryWriter.Write((int)ac.preWrapMode);
                    binaryWriter.Write((int)ac.postWrapMode);

                    int len = ac.length;
                    binaryWriter.Write(len);
                    Keyframe[] keyFrames = ac.keys;
                    for (int i = 0; i < keyFrames.Length; i++)
                    {
                        Keyframe key = keyFrames[i];
                        binaryWriter.Write(key.inTangent);
                        binaryWriter.Write(key.outTangent);
                        binaryWriter.Write(key.tangentMode);
                        binaryWriter.Write(key.time);
                        binaryWriter.Write(key.value);
                    }
                }
                break;
            case UnknownType:
                {
                    throw new System.Exception("unknown type: " + obj.GetType());
                }
            default:
                {
                    WriteByType(binaryWriter, type, obj);
                }
                break;
        }
    }

    public static FieldDictionary ReadFromBytes(byte[] data, Object[] objs)
    {
        if (data == null)
        {
            return null;
        }

        System.IO.MemoryStream stream = new System.IO.MemoryStream(data);
        System.IO.BinaryReader binaryReader = new System.IO.BinaryReader(stream);

        return ReadValueFromBytes(binaryReader, ObjectType, objs) as FieldDictionary;
    }

    public static object ReadValueFromBytes(System.IO.BinaryReader binaryReader, sbyte type, Object[] objs)
    {
        switch (type)
        {
            case UnityObjectType:
                {
                    short index = binaryReader.ReadInt16();
                    if (index < 0)
                    {
                        return null;
                    }
                    else
                    {
                        return objs[index];
                    }
                }
            case ArrayType:
                {
                    int arrLen = binaryReader.ReadInt32();
                    sbyte elemType = binaryReader.ReadSByte();
                    System.Array arr = System.Array.CreateInstance(GetSystemType(elemType), arrLen);

                    for (int i = 0; i < arrLen; i++)
                    {
                        arr.SetValue(ReadValueFromBytes(binaryReader, elemType, objs), i);
                    }

                    return arr;
                }
            case ListType:
                {
                    int arrLen = binaryReader.ReadInt32();
                    sbyte elemType = binaryReader.ReadSByte();
                    List<object> list = new List<object>();
                    // System.Array arr = System.Array.CreateInstance(GetSystemType(elemType), arrLen);

                    for (int i = 0; i < arrLen; i++)
                    {
                        list.Add(ReadValueFromBytes(binaryReader, elemType, objs));
                        // arr.SetValue(ReadValueFromBytes(binaryReader, elemType, objs), i);
                    }

                    return list;
                    // return arr;
                }
            case ObjectType:
                {
                    FieldDictionary objDic = new FieldDictionary();

                    objDic.className = ReadValueFromBytes(binaryReader, StringType, objs) as string;

                    int count = binaryReader.ReadInt32();

                    for (int i = 0; i < count; i++)
                    {
                        string fieldName = binaryReader.ReadString();
                        sbyte type2 = binaryReader.ReadSByte();
                        object value = ReadValueFromBytes(binaryReader, type2, objs);
                        objDic.Add(fieldName, value);
                    }

                    return objDic;
                }
                case CustomType:
                {
                    if(serializeListener != null)
                    {
                        return serializeListener.Read(binaryReader, objs);
                    }
                    else
                    {
                        return null;
                    }
                }

            case AnimationCurveType:
                {
                    AnimationCurve ac = new AnimationCurve();
                    ac.preWrapMode = (WrapMode)binaryReader.ReadInt32();
                    ac.postWrapMode = (WrapMode)binaryReader.ReadInt32();

                    int len = binaryReader.ReadInt32();
                    Keyframe[] keyFrames = new Keyframe[len];
                    for (int i = 0; i < keyFrames.Length; i++)
                    {
                        keyFrames[i].inTangent = binaryReader.ReadSingle();
                        keyFrames[i].outTangent = binaryReader.ReadSingle();
                        keyFrames[i].tangentMode = binaryReader.ReadInt32();
                        keyFrames[i].time = binaryReader.ReadSingle();
                        keyFrames[i].value = binaryReader.ReadSingle();
                    }
                    ac.keys = keyFrames;

                    return ac;
                }
            case UnknownType:
                {
                    throw new System.Exception("unknown type: " + type);
                }
            default:
                {
                    return ReadByType(binaryReader, type);
                }
        }
    }

    static sbyte GetBinaryType(System.Type t)
    {
        if (t == null)
        {
            return NullType;
        }
        else if (t == typeof(System.Char))
        {
            return CharType;
        }
        else if (t == typeof(System.SByte))
        {
            return SByteType;
        }
        else if (t == typeof(System.Int16))
        {
            return Int16Type;
        }
        else if (t == typeof(System.Int32))
        {
            return Int32Type;
        }
        else if (t == typeof(System.Int64))
        {
            return Int64Type;
        }
        else if (t == typeof(System.Byte))
        {
            return ByteType;
        }
        else if (t == typeof(System.UInt16))
        {
            return UInt16Type;
        }
        else if (t == typeof(System.UInt32))
        {
            return UInt32Type;
        }
        else if (t == typeof(System.UInt64))
        {
            return UInt64Type;
        }
        else if (t == typeof(System.Single))
        {
            return SingleType;
        }
        else if (t == typeof(System.Double))
        {
            return DoubleType;
        }
        else if (t == typeof(System.Boolean))
        {
            return BooleanType;
        }
        else if (t == typeof(System.String))
        {
            return StringType;
        }
        else if (t.IsEnum)
        {
            return EnumType;
        }
        else if (t == typeof(Object) || t.IsSubclassOf(typeof(Object)))
        {
            return UnityObjectType;
        }
        else if (t.IsArray)
        {
            return ArrayType;
        }
        else if (t.IsGenericType)
        {
            System.Type listType = typeof(List<>);
            System.Type[] argTypes = t.GetGenericArguments();
            if (argTypes.Length == 1)
            {
                if (t != listType.MakeGenericType(argTypes))
                {
                    return UnknownType;
                }

                return ListType;
            }
            else
            {
                return UnknownType;
            }
        }
        else if (t.IsClass || (t.IsValueType && !t.IsEnum))
        {
            if (t == typeof(AnimationCurve))
            {
                return AnimationCurveType;
            }
            else
            {
                if(serializeListener != null)
                {
                    if(serializeListener.isCustomSerializable(t))
                    {
                        return CustomType;
                    }
                }
            }
            

            return ObjectType;
        }
        else
        {
            return UnknownType;
        }
    }

    static System.Type GetSystemType(short t)
    {
        if (t == NullType)
        {
            return null;
        }
        else if (t == CharType)
        {
            return typeof(System.Char);
        }
        else if (t == SByteType)
        {
            return typeof(System.SByte);
        }
        else if (t == Int16Type)
        {
            return typeof(System.Int16);
        }
        else if (t == Int32Type)
        {
            return typeof(System.Int32);
        }
        else if (t == Int64Type)
        {
            return typeof(System.Int64);
        }
        else if (t == ByteType)
        {
            return typeof(System.Byte);
        }
        else if (t == UInt16Type)
        {
            return typeof(System.UInt16);
        }
        else if (t == UInt32Type)
        {
            return typeof(System.UInt32);
        }
        else if (t == UInt64Type)
        {
            return typeof(System.UInt64);
        }
        else if (t == SingleType)
        {
            return typeof(System.Single);
        }
        else if (t == DoubleType)
        {
            return typeof(System.Double);
        }
        else if (t == BooleanType)
        {
            return typeof(System.Boolean);
        }
        else if (t == StringType)
        {
            return typeof(System.String);
        }
        else if (t == EnumType)
        {
            return typeof(System.Enum);
        }
        else if (t == UnityObjectType)
        {
            return typeof(Object);
        }
        else if (t == ArrayType)
        {
            return typeof(System.Array);
        }
        else if (t == ListType)
        {
            return typeof(List<>);
        }
        else if (t == CustomType)
        {
            if(serializeListener != null)
            {
                return typeof(System.Object);
            }
            else
            {
                return typeof(System.Object);
            }
        }
        else if (t == AnimationCurveType)
        {
            return typeof(AnimationCurve);
        }
        else if (t == ObjectType)
        {
            return typeof(System.Object);
        }
        else
        {
            return null;
        }
    }

    static bool IsTypeSerializable(System.Type t)
    {
        if (t.IsGenericType)
        {
           System.Type listType = typeof(List<>);
           System.Type[] argTypes = t.GetGenericArguments();
           if (argTypes.Length == 1)
           {
               if (t != listType.MakeGenericType(argTypes))
               {
                   return false;
               }

               if (argTypes[0].IsGenericType)
               {
                   return false;
               }

               return IsTypeSerializable(argTypes[0]);
           }

            return false;
        }
        else
        {
            if (t.IsClass || (t.IsValueType && !t.IsEnum))
            {
                if (!t.IsEnum)
                {
                    System.Attribute[] attrs = System.Attribute.GetCustomAttributes(t);
                    int nserTag = 0;
                    nserTag = System.Array.FindIndex(attrs, a => a is System.SerializableAttribute);
                    if (nserTag == -1 && !typeof(UnityEngine.Object).IsAssignableFrom(t))
                    {
                        if (t != typeof(AnimationCurve))
                        {
                            return false;
                        }
                    }
                }
            }
        }

        return true;
    }

    static bool IsFieldSerializable(System.Reflection.FieldInfo fi)
    {
        var attrs = System.Attribute.GetCustomAttributes(fi);
        var nserTag = System.Array.FindIndex(attrs, a => a is System.NonSerializedAttribute);
        if (nserTag != -1)
        {
            return false;
        }

        var serTag = System.Array.FindIndex(attrs, a => a is SerializeField);
        if (!(serTag != -1 || fi.IsPublic))
        {
            return false;
        }

        return IsTypeSerializable(fi.FieldType);
    }

    static System.Reflection.FieldInfo[] GetSerializableFields(System.Type t)
    {
        List<System.Reflection.FieldInfo> fieldList = new List<System.Reflection.FieldInfo>();

        System.Reflection.FieldInfo[] fields = t.GetFields(
            System.Reflection.BindingFlags.Instance |
            System.Reflection.BindingFlags.NonPublic |
            System.Reflection.BindingFlags.Public |
            System.Reflection.BindingFlags.FlattenHierarchy);

        if (fields != null && fields.Length > 0)
        {
            for (int i = 0; i < fields.Length; ++i)
            {
                var fi = fields[i];

                if (IsFieldSerializable(fi))
                {
                    fieldList.Add(fi);
                }
            }
        }

        return fieldList.ToArray();
    }

    public static void AddSerializableType(System.Type type, HashSet<System.Type> typeSet)
    {
        if(typeSet.Contains(type))
        {
            return;
        }

        Queue<System.Type> tq = new Queue<System.Type>();
        tq.Enqueue(type);

        while (tq.Count > 0)
        {
            System.Type t = tq.Dequeue();
            typeSet.Add(t);

            System.Reflection.FieldInfo[] fieldInfos = SerializableFieldContainerHelper.GetSerializableFields(type);
            for (int i = 0; i < fieldInfos.Length; i++)
            {
                System.Reflection.FieldInfo fieldInfo = fieldInfos[i];
                if (SerializableFieldContainerHelper.GetBinaryType(fieldInfo.FieldType) == SerializableFieldContainerHelper.ArrayType)
                {
                    if (SerializableFieldContainerHelper.GetBinaryType(fieldInfo.FieldType.GetElementType()) == SerializableFieldContainerHelper.ObjectType)
                    {
                        if (!typeSet.Contains(fieldInfo.FieldType.GetElementType()))
                        {
                            tq.Enqueue(fieldInfo.FieldType.GetElementType());
                        }
                    }
                }
                else if (SerializableFieldContainerHelper.GetBinaryType(fieldInfo.FieldType) == SerializableFieldContainerHelper.ObjectType)
                {
                    if (!typeSet.Contains(fieldInfo.FieldType))
                    {
                        tq.Enqueue(fieldInfo.FieldType);
                    }
                }
            }
        }
    }



    static void WriteByType(System.IO.BinaryWriter binaryWriter, sbyte type, object obj)
    {
        switch (type)
        {
            case NullType:
                {
                    return;
                }
            case CharType:
                {
                    binaryWriter.Write((char)obj);
                    return;
                }
            case SByteType:
                {
                    binaryWriter.Write((sbyte)obj);
                    return;
                }
            case Int16Type:
                {
                    binaryWriter.Write((short)obj);
                    return;
                }
            case Int32Type:
                {
                    binaryWriter.Write((int)obj);
                    return;
                }
            case Int64Type:
                {
                    binaryWriter.Write((long)obj);
                    return;
                }
            case ByteType:
                {
                    binaryWriter.Write((byte)obj);
                    return;
                }
            case UInt16Type:
                {
                    binaryWriter.Write((ushort)obj);
                    return;
                }
            case UInt32Type:
                {
                    binaryWriter.Write((uint)obj);
                    return;
                }
            case UInt64Type:
                {
                    binaryWriter.Write((ulong)obj);
                    return;
                }
            case SingleType:
                {
                    binaryWriter.Write((float)obj);
                    return;
                }
            case DoubleType:
                {
                    binaryWriter.Write((double)obj);
                    return;
                }
            case BooleanType:
                {
                    binaryWriter.Write((bool)obj);
                    return;
                }
            case StringType:
                {
                    binaryWriter.Write((string)obj);
                    return;
                }
            case EnumType:
                {
                    binaryWriter.Write((int)obj);
                    return;
                }
        }
    }

    static object ReadByType(System.IO.BinaryReader binaryReader, sbyte type)
    {
        switch (type)
        {
            case NullType:
                {
                    return null;
                }
            case CharType:
                {
                    return binaryReader.ReadChar();
                }
            case SByteType:
                {
                    return binaryReader.ReadSByte();
                }
            case Int16Type:
                {
                    return binaryReader.ReadInt16();
                }
            case Int32Type:
                {
                    return binaryReader.ReadInt32();
                }
            case Int64Type:
                {
                    return binaryReader.ReadInt64();
                }
            case ByteType:
                {
                    return binaryReader.ReadByte();
                }
            case UInt16Type:
                {
                    return binaryReader.ReadUInt16();
                }
            case UInt32Type:
                {
                    return binaryReader.ReadUInt32();
                }
            case UInt64Type:
                {
                    return binaryReader.ReadUInt64();
                }
            case SingleType:
                {
                    return binaryReader.ReadSingle();
                }
            case DoubleType:
                {
                    return binaryReader.ReadDouble();
                }
            case BooleanType:
                {
                    return binaryReader.ReadBoolean();
                }
            case StringType:
                {
                    return binaryReader.ReadString();
                }
            case EnumType:
                {
                    return binaryReader.ReadInt32();
                }
        }

        return null;
    }
}

}